package com.hk.core.data.r2dbc.interceptor;

import com.hk.commons.multischema.DataSourceType;
import com.hk.commons.util.CollectionUtils;
import com.hk.commons.util.StringUtils;
import com.hk.core.data.r2dbc.connection.DynamicRoutingConnectionFactory;
import com.hk.core.data.r2dbc.tenants.ReactiveTenantIdResolver;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.reactivestreams.Publisher;
import org.springframework.core.annotation.AnnotationUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;

/**
 * @param resolvers
 * @author Kevin
 * @date 2024-06-08 22:35
 */
public record R2dbcMultiDatasourceAdvice(List<ReactiveTenantIdResolver> resolvers,
                                         Class<?> repositoryInterface) implements MethodInterceptor {

    @Override
    public Publisher<?> invoke(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        String db = lookupForMethod(method);
        if (!StringUtils.hasText(db)) {
            db = lookupForClass(repositoryInterface);
        }
        return DynamicRoutingConnectionFactory.putR2dbcDataSource((Publisher<?>) invocation.proceed(), resolver(db));
    }

    private Mono<String> resolver(String dbName) {
        if (StringUtils.hasLength(dbName) && CollectionUtils.isNotEmpty(resolvers)) {
            return Flux.fromIterable(resolvers)
                    .flatMap(resolver -> resolver.resolve(dbName))
                    .filter(StringUtils::hasLength)
                    .next();
        }
        return Mono.justOrEmpty(dbName);
    }

    private String lookupForMethod(Method method) {
        DataSourceType sourceType = method.getAnnotation(DataSourceType.class);
        return Objects.nonNull(sourceType) ? sourceType.value() : null;
    }

    private String lookupForClass(Class<?> clazz) {
        if (Objects.nonNull(clazz)) {
            DataSourceType sourceType = AnnotationUtils.findAnnotation(clazz, DataSourceType.class);
            return Objects.nonNull(sourceType) ? sourceType.value() : null;
        }
        return null;
    }
}
